
/* DaoCLoader:
 * Running time wrapping of C functions loaded from dynamic linking library.
 * Copyright (C) 2008-2011, Limin Fu (phoolimin@gmail.com).
 */

#include"string.h"
#include"daoType.h"
#include"daoClass.h"
#include"daoObject.h"
#include"daoRoutine.h"
#include"daoNumtype.h"
#include"daoContext.h"
#include"daoProcess.h"
#include"daoVmspace.h"
#include"daoNamespace.h"
#include"daoParser.h"

#include"ffi.h"

DAO_INIT_MODULE;

typedef void* (*bare_func)();
typedef struct DaoFFI DaoFFI;

enum
{
	DAO_FFI_INT ,
	DAO_FFI_UINT8 ,
	DAO_FFI_SINT8 ,
	DAO_FFI_UINT16 ,
	DAO_FFI_SINT16 ,
	DAO_FFI_UINT32 ,
	DAO_FFI_SINT32 
};

struct DaoFFI
{
	void      *fptr;
	ffi_cif    cif;
	ffi_type  *args[ DAO_MAX_PARAM + 1 ];
	ffi_type  *retype;
};


static void DaoCLoader_Execute( DaoContext *ctx, DValue *p[], int N )
{
	DaoFunction *func = ctx->thisFunction;
	DaoType *routype, *tp, *itp, **nested;
	DaoArray *array;
	DaoCData *cdata;
	DaoFFI *ffi;
	DString *str = NULL;
	complex16 com = { 0.0, 0.0 };
	char *chs;
	void **args;
	double dummy;
	void *ret = & dummy;
	int i;
	if( func == NULL ) return;
	if( func->ffiData == NULL ) return;
	//printf( "ffiData = %p\n", func->ffiData );
	ffi = func->ffiData;
	routype = func->routType;
	nested = routype->nested->items.pType;
	/*
	printf( "DaoCLoader_Execute(): %s\n", func->routType->name->mbs );
	printf( "params: %i %i\n", func->parCount, routype->nested->size );
	*/
	args = (void*)alloca( func->parCount * sizeof(void*) );
	memset( args, 0, func->parCount * sizeof(void*) );
	for(i=0; i<func->parCount; i++){
		if( i >= routype->nested->size ) continue;
		tp = nested[i];
		if( tp->tid == DAO_PAR_NAMED || tp->tid == DAO_PAR_DEFAULT ) tp = tp->aux.v.type;
		//printf( "%i  %i  %s\n", i, tp->tid, tp->name->mbs );
		switch( tp->tid ){
			case DAO_INTEGER :
			case DAO_FLOAT :
			case DAO_DOUBLE :
				args[i] = & p[i]->v;
				break;
			case DAO_COMPLEX :
				args[i] = & p[i]->v.c;
				break;
			case DAO_STRING :
				if( tp->ffitype && p[i]->v.s->mbs ){
					DString_ToWCS( p[i]->v.s );
				}else if( tp->ffitype ==0 && p[i]->v.s->mbs ==NULL ){
					DString_ToMBS( p[i]->v.s );
				}
				if( p[i]->v.s->mbs ){
					args[i] = & p[i]->v.s->mbs;
				}else{
					args[i] = & p[i]->v.s->wcs;
				}
				break;
			case DAO_ARRAY :
				itp = tp->nested->size ? tp->nested->items.pType[0] : NULL;
				array = p[i]->v.array;
				args[i] = & array->data.p;
				if( itp == NULL ) break;
				switch( itp->tid ){
					case DAO_INTEGER :
						switch( itp->ffitype ){
							case DAO_FFI_INT : DaoArray_ToInt( array ); break;
							case DAO_FFI_UINT8 : DaoArray_ToUByte( array ); break;
							case DAO_FFI_SINT8 : DaoArray_ToByte( array ); break;
							case DAO_FFI_UINT16 : DaoArray_ToUShort( array ); break;
							case DAO_FFI_SINT16 : DaoArray_ToShort( array ); break;
							case DAO_FFI_UINT32 : DaoArray_ToUInt( array ); break;
							default : DaoArray_ToInt( array ); break;
						}
						break;
					case DAO_FLOAT :
						DaoArray_ToFloat( p[i]->v.array );
						break;
					case DAO_DOUBLE :
						DaoArray_ToDouble( p[i]->v.array );
						break;
					case DAO_COMPLEX :
						break;
					default : break;
				}
				args[i] = & array->data.p;
				break;
			case DAO_CDATA :
				args[i] = & p[i]->v.cdata->data;
				break;
			default :
				args[i] = & p[i]->v.p;
				break;
		}
	}
	//printf( "debug 1\n" );
	tp = routype->aux.v.type;
	if( tp ){
		switch( tp->tid ){
			case DAO_INTEGER :
				ret = (void*) DaoContext_PutInteger( ctx, 0 );
				break;
			case DAO_FLOAT :
				ret = (void*) DaoContext_PutFloat( ctx, 0.0 );
				break;
			case DAO_DOUBLE :
				ret = (void*) DaoContext_PutDouble( ctx, 0.0 );
				break;
			case DAO_COMPLEX :
				ret = (void*) DaoContext_PutComplex( ctx, com );
				break;
			case DAO_STRING :
				str = DaoContext_PutMBString( ctx, "" );
				DString_Detach( str );
				ret = (void*) & str->mbs;
				break;
			case DAO_CDATA :
				cdata = DaoContext_PutCData( ctx, NULL, tp->typer );
				ret = & cdata->data;
				break;
			default : break;
		}
	}
	ffi_call( & ffi->cif, ffi->fptr, ret, args );
	if( tp ){
		switch( tp->tid ){
			case DAO_STRING :
				if( ret ){
					str->size = strlen( str->mbs );
					str->bufSize = str->size + 1;
				}
				break;
			default : break;
		}
	}
	for(i=0; i<func->parCount; i++){
		if( i >= routype->nested->size ) continue;
		tp = nested[i];
		switch( tp->tid ){
			case DAO_STRING :
				if( p[i]->v.s->mbs ){
					p[i]->v.s->size = strlen( p[i]->v.s->mbs );
					p[i]->v.s->bufSize = p[i]->v.s->size + 1;
				}else{
					p[i]->v.s->size = wcslen( p[i]->v.s->wcs );
					p[i]->v.s->bufSize = p[i]->v.s->size + 1;
				}
				break;
			case DAO_ARRAY :
				itp = tp->nested->size ? tp->nested->items.pType[0] : NULL;
				array = p[i]->v.array;
				if( itp == NULL ) break;
				switch( itp->tid ){
					case DAO_INTEGER :
						switch( itp->ffitype ){
							case DAO_FFI_INT : DaoArray_FromInt( array ); break;
							case DAO_FFI_UINT8 : DaoArray_FromUByte( array ); break;
							case DAO_FFI_SINT8 : DaoArray_FromByte( array ); break;
							case DAO_FFI_UINT16 : DaoArray_FromUShort( array ); break;
							case DAO_FFI_SINT16 : DaoArray_FromShort( array ); break;
							case DAO_FFI_UINT32 : DaoArray_FromUInt( array ); break;
							default : DaoArray_FromInt( array ); break;
						}
						break;
					case DAO_FLOAT :
						DaoArray_FromFloat( p[i]->v.array );
						break;
					case DAO_DOUBLE :
						DaoArray_FromDouble( p[i]->v.array );
						break;
					case DAO_COMPLEX :
						break;
					default : break;
				}
				break;
			default : break;
		}
	}
}

static ffi_type *intps[] = {
	& ffi_type_sint32,
	& ffi_type_uint8,
	& ffi_type_sint8,
	& ffi_type_uint16,
	& ffi_type_sint16,
	& ffi_type_uint32,
	& ffi_type_sint32
};
static ffi_type* ConvertType( DaoType *tp )
{
	ffi_type *ffitype = & ffi_type_pointer;
	if( tp == NULL ) return ffitype;
	switch( tp->tid ){
		case DAO_INTEGER :
			ffitype = & ffi_type_sint32;		
			if( tp->ffitype <= DAO_FFI_SINT32 ) ffitype = intps[tp->ffitype];
			break;
		case DAO_FLOAT :
			ffitype = & ffi_type_float;
			break;
		case DAO_DOUBLE :
			ffitype = & ffi_type_double;
			break;
		default : break;
	}
	return ffitype;
}
static void DaoCLoader_Load( DaoContext *ctx, DValue *p[], int N )
{
	DValue value;
	DString *str;
	DString *lib = p[0]->v.s;
	DaoList *funames = p[1]->v.list;
	DaoList *tpnames = p[2]->v.list;
	DaoFunction *func;
	DaoVmSpace *vms = ctx->vmSpace;
	DaoNameSpace *ns = ctx->nameSpace;
	DaoType *routype, *tp, **nested;
	DaoParser *parser;
	DaoParser *defparser;
	DaoTypeBase *typer;
	DaoCDataCore *core;
	DaoCData *proto;
	DaoFFI *ffi;
	void *handle, *fptr;
	char *chs;
	int i, j;
	int ok = 1;
	DString_ToMBS( lib );
	handle = DaoLoadLibrary( lib->mbs );
	if( handle == NULL ){
		printf( "%s\n", dlerror() );
		return;
	}
	parser = DaoParser_New();
	parser->vmSpace = vms;
	parser->nameSpace = ns;
	parser->defParser = defparser = DaoParser_New();
	defparser->vmSpace = vms;
	defparser->nameSpace = ns;
	defparser->routine = ns->routEvalConst;
	for(i=0; i<tpnames->items->size; i++){
		str = tpnames->items->data[i].v.s;
		DString_ToMBS( str );
		value = DaoNameSpace_GetData( ns, str );
		if( value.t ) continue; /* warn XXX */
		typer = calloc( 1, sizeof(DaoTypeBase) );
		chs = malloc( (str->size+1) * sizeof(char) );
		strncpy( chs, str->mbs, str->size );
		chs[ str->size ] = 0;
		typer->name = chs;
		typer->priv = (DaoTypeCore*) DaoCDataCore_New();
		typer->priv->nspace = ns;
		DaoTypeCData_SetMethods( typer );
		value = daoNullCData;
		value.v.cdata = DaoCData_New( typer, NULL );
		DaoNameSpace_AddConst( ns, str, value, DAO_DATA_PUBLIC );
		DaoNameSpace_MakeType( ns, str->mbs, DAO_CDATA, value.v.p, NULL, 0 );
	}
	for(i=0; i<funames->items->size; i++){
		str = funames->items->data[i].v.s;
		DString_ToMBS( str );
    if( str->size == 0 ) continue;
		func = DaoNameSpace_MakeFunction( ns, str->mbs, parser );
		//func = DaoNameSpace_ParsePrototype( ns, str->mbs, parser );
		if( func ){
			routype = func->routType;
			nested = routype->nested->items.pType;
			//printf( "%s\n", func->routType->name->mbs );
			func->pFunc = DaoCLoader_Execute;
			fptr = DaoFindSymbol( handle, func->routName->mbs );
			ffi = NULL;
			if( fptr ){
				ffi = (DaoFFI*) calloc( 1, sizeof(DaoFFI) );
				ffi->fptr = fptr;
				for(j=0; j<func->parCount; j++){
					tp = nested[j];
					if( tp->tid ==DAO_PAR_NAMED || tp->tid ==DAO_PAR_DEFAULT )
						tp = tp->aux.v.type;
					ffi->args[j] = ConvertType( tp );
				}
				ffi->retype = ConvertType( routype->aux.v.type );
				if( ffi_prep_cif( & ffi->cif, FFI_DEFAULT_ABI, func->parCount, 
							ffi->retype, ffi->args ) != FFI_OK ){
					free( ffi );
					ffi = NULL;
				}
			}else{
				ok = 0;
				printf( "WARNING: symbol %s is not found!\n", func->routName->mbs );
      }
			func->ffiData = ffi;
		}
	}
	DaoParser_Delete( parser );
	DaoParser_Delete( defparser );
	if( ok ==0 ) DaoContext_RaiseException( ctx, DAO_ERROR, "loading failed" );
}
int DaoOnLoad( DaoVmSpace *vms, DaoNameSpace *ns )
{
	DaoType *tp;
	const char *const ctype[] = { "uint8","sint8","uint16","sint16","uint32","sint32" };
	const char *const alias[] = { "uchar","char","ushort","short","uint","int" };
	int i;

	tp = DaoNameSpace_MakeType( ns, "int", DAO_INTEGER, 0,0,0 );
	for(i=0; i<DAO_FFI_SINT32; i++){
		tp = DaoType_Copy( tp );
		tp->ffitype = i + 1;
		if( tp->ffitype != DAO_FFI_SINT32 ){
			DString_SetMBS( tp->name, alias[i] );
			DaoNameSpace_AddType( ns, tp->name, tp );
		}
		DString_SetMBS( tp->name, ctype[i] );
		DaoNameSpace_AddType( ns, tp->name, tp );
	}

	tp = DaoNameSpace_MakeType( ns, "string", DAO_STRING, 0,0,0 );
	tp = DaoType_Copy( tp );
	tp->ffitype = 1;
	DString_SetMBS( tp->name, "wstring" );
	DaoNameSpace_AddType( ns, tp->name, tp );

	DaoNameSpace_WrapFunction( ns, DaoCLoader_Load, 
			"dlopen( lib : string, funcs : list<string>, ctypes : list<string> ={} )" );
	return 0;
}
